use std::collections::HashMap;

use common_base::{prelude::*, serde_json};

use crate::invoker::TryIntoResponseObject;

use super::{ContextValue, DataModel};

#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ResponseObject {
    pub(crate) error_code: i32,
    pub(crate) descriptor: HashMap<String, ContextValue>,
    #[serde(serialize_with = "serialize_payload")]
    #[serde(deserialize_with = "deserialize_payload")]
    pub(crate) payload: Option<ResponsePayload>,
}

fn serialize_payload<S>(
    value: &Option<ResponsePayload>,
    serializer: S,
) -> std::result::Result<S::Ok, S::Error>
where
    S: serde::Serializer,
{
    match value {
        Some(ResponsePayload::Landing(Some(l))) => {
            serializer.serialize_bytes(l.into_bincode().as_slice())
        }
        Some(ResponsePayload::Landing(None)) => serializer.serialize_none(),
        Some(ResponsePayload::Flight(f)) => serializer.serialize_bytes(f),
        None => serializer.serialize_none(),
    }
}

fn deserialize_payload<'de, D>(
    deserializer: D,
) -> std::result::Result<Option<ResponsePayload>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    struct OptionPayloadVisitor;
    impl<'de> serde::de::Visitor<'de> for OptionPayloadVisitor {
        type Value = Option<ResponsePayload>;

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            formatter.write_str("Option<ResponsePayload>")
        }

        fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
        where
            D: serde::Deserializer<'de>,
        {
            struct PayloadVisitor;
            impl<'de> serde::de::Visitor<'de> for PayloadVisitor {
                type Value = Vec<u8>;

                fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                    formatter.write_str("Payload::Flight(Vec<u8>)")
                }
            }

            Ok(Some(ResponsePayload::Flight(
                deserializer.deserialize_bytes(PayloadVisitor)?,
            )))
        }

        fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            Ok(None)
        }
    }

    deserializer.deserialize_option(OptionPayloadVisitor)
}

impl ResponseObject {
    pub fn serializes_into_json(self) -> serde_json::Value {
        let response = if let Some(ResponsePayload::Landing(Some(r))) = self.payload.as_ref() {
            r.into_json_value()
        } else {
            serde_json::Value::Null
        };
        serde_json::json!({
            "errorCode": self.error_code,
            "descriptor": self.descriptor,
            "response": response
        })
    }

    pub fn deserizlize_from_bincode(response_object_bytes: Vec<u8>) -> Result<Self> {
        bincode::deserialize_from(response_object_bytes.as_slice()).map_err(|e| ErrorCode::from(e))
    }
}

impl ResponseObject {
    pub fn get_payload_mut(&mut self) -> Option<&mut Box<dyn DataModel>> {
        if let Some(ResponsePayload::Landing(Some(p))) = self.payload.as_mut() {
            Some(p)
        } else {
            None
        }
    }
}

impl TryIntoResponseObject for ResponseObject {
    fn try_into_ro(&self) -> Result<ResponseObject> {
        unreachable!("ResponseObject can not into it self")
    }
}

#[derive(Debug)]
pub enum ResponsePayload {
    Landing(Option<Box<dyn DataModel>>),
    Flight(Vec<u8>),
}
